#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <getopt.h>
#include <semaphore.h>

#include <clii264e.h>
#include <cliparse.h>
#ifdef DMALLOC
#include <imp/dmalloc.h>
#endif
typedef struct {
	cli_opt_t	*opt;
	sem_t		sem_idx_r;
	sem_t		sem_idx_w;
	i264e_pic_t	*pic_in;
	volatile int *ctl;
} thread_read_pic_t;

int init(int argc, char **argv, i264e_param_t *param, cli_opt_t *opt)
{
	memset(param, 0, sizeof(i264e_param_t));
	memset(opt, 0, sizeof(cli_opt_t));
	i264e_param_default(param);
	opt->b_progress = 1;
	if (parse(argc, argv, param, opt) < 0) {
		fprintf(stderr, "parse param error\n");
		goto err_parse_param;
	}
	opt->reuse_cnt = C_MAX(opt->reuse_cnt, 1);
	opt->pic_cnt = C_MAX(opt->pic_cnt, 1);
	opt->b_use_phy = param->b_use_phy;
	if (param->b_use_phy) {
		opt->ckmalloc = param->ckmalloc = c_kalloc;
		opt->ckfree = param->ckfree = c_kfree;
		opt->ckvirt_to_phys = param->ckvirt_to_phys = c_kvirt_to_kphys;
		opt->ckphys_to_virt = param->ckphys_to_virt = c_kphys_to_kvirt;
	} else {
		opt->ckmalloc = param->ckmalloc = c_malloc;
		opt->ckfree = param->ckfree = c_free;
		opt->ckvirt_to_phys = param->ckvirt_to_phys = c_virt_to_phys;
		opt->ckphys_to_virt = param->ckphys_to_virt = c_phys_to_virt;
	}
	/*select input raw*/
	if (opt->input == 0)
		opt->cli_input = ((cli_input_t *)&cli_input_raw);
	/*select input v4l2*/
	else if (opt->input == 1)
		opt->cli_input = ((cli_input_t *)&cli_input_v4l2);
	else {
		fprintf(stderr, "no input select\n");
		goto err_no_input_select;
	}
	if (opt->input == 0) {
		if (NULL == opt->input_file_name) {
			fprintf(stderr, "no input file\n");
			goto err_no_input_file;
		}
		if (opt->cli_input->open_file(opt->input_file_name, opt) < 0) {
			fprintf(stderr, "cli_input open raw failed failed\n");
			goto err_cli_open_inputfile;
		}
	} else if (opt->input == 1) {
		if (opt->cli_input->open_file(NULL, opt) < 0) {
			fprintf(stderr, "cli_input open v4l2 failed failed\n");
			goto err_cli_open_inputfile;
		}
	} else {
		fprintf(stderr, "no input select\n");
		goto err_no_input_select;
	}
	/*select input thread*/
	if(opt->b_thread_input/* || param->i_threads > 1*/)
	{
		if(thread_input.open_file(NULL, opt))
		{
			fprintf(stderr, "threaded input failed\n");
			return -1;
		}
		opt->cli_input = ((cli_input_t *)&thread_input);
	}
	if (NULL == opt->output_file_name)
	{
		fprintf(stderr, "no output file\n");
		goto err_no_output_file;
	}
	if ((opt->output_file  = fopen(opt->output_file_name, "w+")) == NULL) {
		fprintf(stderr, "fopen %s failed:%s\n", opt->output_file_name, strerror(errno));
		goto err_open_output_file;
	}

	if (opt->i_csp != param->i_csp) {
		param->b_user_fenc = 0;
	}
	/* alloc user nal buffer */
	int i = 0;
	if (param->b_user_nalbuffer) {
		int nal_size = C_ALIGN(param->i_width, 16)*C_ALIGN(param->i_height, 16)*3/2 + 4096 + 64 * 2;
		opt->nal_buffer = malloc(param->i_threads*sizeof(uint8_t*));
		if (!opt->nal_buffer)
			goto err_nal_malloc;
		for (i = 0; i < param->i_threads; i++) {
			opt->nal_buffer[i] = c_malloc(-1, nal_size, C_VB_ALIGN);
			if (!opt->nal_buffer[i])
				goto err_nal_buf_malloc;
		}
	}

	return 0;
err_nal_buf_malloc:
	for (; i > 0; i--)
		c_free(-1, opt->nal_buffer[i-1]);
	free(opt->nal_buffer);
err_nal_malloc:
err_open_output_file:
err_no_output_file:
err_cli_open_inputfile:
err_no_input_file:
err_no_input_select:
	deparse(param, opt);
err_parse_param:
	return -1;
}

static int64_t i264e_print_status( int64_t i_start, int64_t i_previous, int i_frame, int i_frame_total, i264e_param_t *param)
{
	char buf[200];
	int64_t i_time = c_mdate();

	if(i_previous && i_time - i_previous < 250000 )
		return i_previous;

	int64_t i_elapsed = i_time - i_start;
	double fps = i_elapsed > 0 ? i_frame * 1000000. / i_elapsed : 0;
	if( i_frame_total )
	{
			sprintf( buf, "i264e [%.1f%%] %d/%d frames, %.2lf fps",
					                 100. * i_frame / i_frame_total, i_frame, i_frame_total, fps);
	}
	else
		sprintf( buf, "i264e %d frames: %.2lf fps", i_frame, fps);
	fprintf( stderr, "%s\r", buf+5 );
	return i_time;
}

void *save_bitsteam(void *arg)
{
	int i = 0, j = 0;
	int64_t i_start = 0;
	i264e_nal_t *nal;
	int i_nal;
	int i_frame_size = 0;
	i264e_pic_t *pic_in, *pic_out;
	save_info_t *save_info = (save_info_t *)arg;
	int64_t i_previous = 0;
	int i_frame_output = 0;
	void *bshandler = NULL, *thandler = NULL;
	i_start = c_mdate();
	for (i = 0; !(*(save_info->ctl)) && (i < save_info->opt->totalframe); i++) {
		pthread_testcancel();
		pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
		if ((i_frame_size = i264e_get_bitstream(save_info->h, &nal, &i_nal, &pic_in, &pic_out,
						&bshandler, &thandler)) < 0) {
			fprintf(stderr, "get_bitstream failed\n");
			goto err_i264e_get_bitstream;
		}
		for (j = 0; j < i_nal; j++) {
			if (fwrite(nal[j].p_payload, 1, nal[j].i_payload, save_info->opt->output_file)
					!= nal[j].i_payload) {
				fprintf(stderr, "fwrite p_nal->p_payload:%p bytes:%d failed:%s\n",
						nal[j].p_payload, nal[j].i_payload, strerror(errno));
				goto err_fwrite_nal;
			}
		}
		if (i264e_release_bitstream(save_info->h, bshandler, thandler) < 0) {
			fprintf(stderr, "i264e_release_bitstream failed\n");
			goto err_i264e_release_bitstream;
		}
		pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
		if (i_frame_size)
			i_frame_output++;
		if (save_info->opt->b_progress)
			i_previous = i264e_print_status(i_start, i_previous, i_frame_output,
					save_info->opt->totalframe, save_info->param);
	}

	if (save_info->opt->b_progress)
		fprintf(stderr, "                                                     \r");

	return 0;

err_i264e_release_bitstream:
err_fwrite_nal:
err_i264e_get_bitstream:
	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
	*(save_info->ctl) = 1;
	return (void *)-1;
}

void *read_frame(void *arg)
{
	int i = 0;
	thread_read_pic_t *trp = (thread_read_pic_t *)arg;

	for (i = 0; !(*(trp->ctl)) && (i < ((trp->opt->totalframe + trp->opt->reuse_cnt - 1) / trp->opt->reuse_cnt)); i++) {
		sem_wait(&trp->sem_idx_w);
		trp->opt->i_frame = i;
		if ((trp->opt->read_cnt <= 0) || ((trp->opt->read_cnt > 0) && (i < trp->opt->read_cnt))) {
			if (trp->opt->cli_input->read_frame(&trp->pic_in[i%trp->opt->pic_cnt], trp->opt) < 0) {
				fprintf(stderr, "read_frame failed\n");
				goto err_i264e_read_frame;
			}
		}
		sem_post(&trp->sem_idx_r);
	}

	return (void *)0;

err_i264e_read_frame:
	*(trp->ctl) = 1;
	return (void *)-1;
}

int encode(i264e_param_t *param, cli_opt_t *opt, volatile int *ctl)
{
	int i = 0;
	i264e_t *h;
	i264e_pic_t *pic_in;
	int pic_index = 0;
	thread_read_pic_t trp;
	pthread_t trp_tid;

	if ((pic_in = calloc(opt->pic_cnt, sizeof(i264e_pic_t))) == NULL) {
		fprintf(stderr, "calloc pic_in failed\n");
		goto err_calloc_pic_in;
	}

	memset(&trp, 0, sizeof(trp));
	trp.opt = opt;
	sem_init(&trp.sem_idx_r, 0, 0);
	sem_init(&trp.sem_idx_w, 0, opt->pic_cnt);
	trp.pic_in = pic_in;
	trp.ctl = ctl;

	for (i = 0; i < opt->pic_cnt; i++) {
		if (opt->cli_input->picture_alloc(&pic_in[i], opt) < 0) {
			fprintf(stderr, "picture_alloc failed\n");
			goto err_picture_alloc;
		}
	}
	if ((h = i264e_init(param)) == NULL) {
		fprintf(stderr, "i264e_init failed\n");
		goto err_i264e_init;
	}

	if (pthread_create(&trp_tid, NULL, read_frame, &trp) < 0) {
		fprintf(stderr, "pthread_create read_frame failed\n");
		goto err_pthread_create_read_frame;
	}

	save_info_t save_info = {
		.h = h,
		.ctl = ctl,
		.opt = opt,
		.param = param,
	};

	if (pthread_create(&opt->output_tid, NULL, save_bitsteam, &save_info) < 0) {
		fprintf(stderr, "pthread_create save_bitsteam failed\n");
		goto err_pthread_create_save_bitstream;
	}

	for (i = 0; !(*ctl) && (i < opt->totalframe); i++) {
		if ((i % opt->reuse_cnt) == 0) {
			sem_wait(&trp.sem_idx_r);
		}
		pic_index = (i / opt->reuse_cnt) % opt->pic_cnt;
		pic_in[pic_index].i_pts = pic_in[pic_index].i_dts = i;
		if (param->b_user_nalbuffer)
			pic_in[pic_index].nals_buffer = &(opt->nal_buffer[i%param->i_threads]);
		if (i264e_encode(h, &pic_in[pic_index]) < 0) {
			fprintf(stderr, "i264e_encode failed\n");
			goto err_i264e_encode;
		}

		if (((i % opt->reuse_cnt) == (opt->reuse_cnt - 1)) || (*ctl) || (i == (opt->totalframe - 1))) {
			if (opt->cli_input->release_frame(&pic_in[pic_index], opt) < 0) {
				fprintf(stderr, "release_frame[%d] failed, i = %d\n", pic_index, i);
				goto err_release_frame;
			}
			sem_post(&trp.sem_idx_w);
		}
		//printf("finished frame %d\n", i);
	}

	//pthread_cancel(opt->output_tid);
	pthread_join(opt->output_tid, NULL);
	pthread_cancel(trp_tid);
	pthread_join(trp_tid, NULL);

	i264e_deinit(h);

	for (i = opt->pic_cnt - 1; i >= 0; i--) {
		opt->cli_input->picture_clean(&pic_in[i], opt);
	}

	sem_destroy(&trp.sem_idx_r);
	sem_destroy(&trp.sem_idx_w);

	free(pic_in);

	return 0;

err_release_frame:
err_i264e_encode:
	sem_post(&trp.sem_idx_w);
	pthread_cancel(opt->output_tid);
	pthread_join(opt->output_tid, NULL);
err_pthread_create_save_bitstream:
	pthread_cancel(trp_tid);
	pthread_join(trp_tid, NULL);
err_pthread_create_read_frame:
	i264e_deinit(h);
err_i264e_init:
	i = opt->pic_cnt - 1;
err_picture_alloc:
	for (; i >= 0; i--) {
		opt->cli_input->picture_clean(&pic_in[i], opt);
	}
	free(pic_in);
err_calloc_pic_in:
	return -1;
}

void deinit(const i264e_param_t *param, const cli_opt_t *opt)
{
	int i = 0;
	if (param->b_user_nalbuffer) {
		for (; i < param->i_threads; i++)
			c_free(-1, opt->nal_buffer[i]);
		free(opt->nal_buffer);
	}
	fclose(opt->output_file);
	opt->cli_input->close_file((cli_opt_t *)opt);
	deparse(param, opt);
}
